package org.ccay.validator.impl;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import org.ccay.core.log.ILogger;
import org.ccay.core.log.CcayLoggerFactory;
//import org.ccay.core.util.StreamUtil;
import org.ccay.core.util.StringUtil;
import org.ccay.validator.IValidatorContext;
import org.ccay.validator.IValidatorContextAware;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.providers.XmlHelper;
import com.opensymphony.xwork2.util.DomHelper;
import com.opensymphony.xwork2.validator.DefaultValidatorFileParser;
import com.opensymphony.xwork2.validator.ValidatorConfig;
import com.opensymphony.xwork2.validator.ValidatorFactory;

/**
 * Parse the validation file. (eg. MyAction-validation.xml,
 * MyAction-actionAlias-validation.xml) to return a List of ValidatorConfig
 * encapsulating the validator information.
 * 
 * @author chaowangbang
 * 
 * @see com.opensymphony.xwork2.validator.ValidatorConfig
 */
public class DefaultValidatorParser extends DefaultValidatorFileParser
		implements IValidatorContextAware {

	/** 日志类 */
	private final ILogger logger = CcayLoggerFactory
			.getLogger(DefaultValidatorParser.class);

	/** dtd信息 */
	private Map<String, String> dtdMappings;

	/** 验证上下文 */
	private IValidatorContext validatorContext;

	/**
	 * 默认构造方法 注册dtd信息
	 */
	public DefaultValidatorParser() {
		Map<String, String> mappings = new HashMap<String, String>();
		mappings.put("-//Ccay Technology//Ccay Validator 1.0//EN",
				"ccay-validator-1.0.dtd");
		mappings.put("-//OpenSymphony Group//XWork Validator 1.0//EN",
				"xwork-validator-1.0.dtd");
		mappings.put("-//OpenSymphony Group//XWork Validator 1.0.2//EN",
				"xwork-validator-1.0.2.dtd");
		mappings.put("-//OpenSymphony Group//XWork Validator 1.0.3//EN",
				"xwork-validator-1.0.3.dtd");
		setDtdMappings(mappings);
	}

	/**
	 * 注册dtd信息为不可修改Map
	 * 
	 * @param mappings
	 */
	public void setDtdMappings(Map<String, String> mappings) {
		this.dtdMappings = Collections.unmodifiableMap(mappings);
	}

	/*
	 * （非 Javadoc）
	 * 
	 * @see com.opensymphony.xwork2.validator.DefaultValidatorFileParser#
	 * parseActionValidatorConfigs
	 * (com.opensymphony.xwork2.validator.ValidatorFactory, java.io.InputStream,
	 * java.lang.String)
	 */
	@Override
	public List<ValidatorConfig> parseActionValidatorConfigs(
			ValidatorFactory validatorFactory, InputStream is,
			final String resourceName) {
		List<ValidatorConfig> validatorCfgs = new ArrayList<ValidatorConfig>();

		InputSource in = new InputSource(is);
		in.setSystemId(resourceName);
		Document doc = DomHelper.parse(in, dtdMappings);
		// 获取参数的索引
		String paramArgIndex = validatorContext.getParam("argIndex",
				String.class);
		// 获取方法的名字
		String paramMethodName = validatorContext.getParam("methodName",
				String.class);
		// 获取解析节点名称
		String tagName = validatorContext.getParam("tagName", String.class);
		Element targetElement = null;
		if (null == tagName) {
			// 如果没设置解析节点名称，则默认解析arg
			NodeList methodNodes = doc.getElementsByTagName("method");
			Element methodElement = findElement(methodNodes, "name",
					paramMethodName);
			// 如果没找到method对应的节点，则返回
			if (methodElement == null) {
				return validatorCfgs;
			}

			NodeList argNodes = methodElement.getElementsByTagName("arg");
			targetElement = findElement(argNodes, "index", paramArgIndex);
		} else {
			// 设置解析节点名称的情况

			// 获取节点名称（匹配element标签）
			String elementName = validatorContext.getParam("elementName",
					String.class);
			NodeList elementNodes = doc.getElementsByTagName("element");
			targetElement = findElement(elementNodes, "name", elementName);
		}
		// 如果没找到arg对应的节点，则查找extends属性
		if (targetElement == null) {
			return validatorCfgs;
		}
		NodeList abstractNodes = doc.getElementsByTagName("abstract");
		Map<String, Element> abstractNodeMap = convert2Map(abstractNodes);
		validatorCfgs = buildParentsFromString(validatorFactory, targetElement,
				abstractNodeMap);

		return validatorCfgs;
	}

	/**
	 * @param nodeList
	 * @param attributeName
	 * @param attributeValue
	 * @return
	 */
	private Element findElement(NodeList nodeList, String attributeName,
			String attributeValue) {
		for (int j = 0; j < nodeList.getLength(); j++) {
			Element element = (Element) nodeList.item(j);
			String attrName = element.getAttribute(attributeName);
			if (attrName.equals(attributeValue)) {
				return element;
			}
		}
		return null;
	}

	/**
	 * @param nodeList
	 * @return
	 */
	private Map<String, Element> convert2Map(NodeList nodeList) {
		Map<String, Element> elementMap = new LinkedHashMap<String, Element>();
		for (int i = 0; i < nodeList.getLength(); i++) {
			Element element = (Element) nodeList.item(i);
			String key = element.getAttribute("name");
			elementMap.put(key, element);
		}
		return elementMap;
	}

	/**
	 * @param argElement
	 * @param abstractNodeMap
	 * @return
	 */
	private List<ValidatorConfig> buildParentsFromString(
			ValidatorFactory validatorFactory, Element argElement,
			Map<String, Element> abstractNodeMap) {
		NodeList validatorNodes = argElement.getElementsByTagName("validator");
		List<ValidatorConfig> validatorCfgs = addValidatorConfigs(
				validatorFactory, validatorNodes, new HashMap<String, String>());
		String parent = argElement.getAttribute("extends");

		NodeList fieldNodes = argElement.getElementsByTagName("field");
		Map<String, Element> fieldNodeMap = convert2Map(fieldNodes);

		String[] parentNames = parent.split(",");
		// 如果extends不存在，则直接返回
		if ("null".equals(parent) || StringUtil.isNullOrEmpty(parent)
				|| StringUtil.isNullOrEmpty(parent.trim())) {
			parentNames = new String[0];
		}

		if (parentNames.length > 0 && !abstractNodeMap.isEmpty()) {
			for (int i = parentNames.length - 1; i > -1; i--) {
				String parentName = parentNames[i];
				Element parentNode = abstractNodeMap.get(parentName);
				NodeList parentValidatorNodes = parentNode
						.getElementsByTagName("validator");
				List<ValidatorConfig> parentValidatorCfgs = addValidatorConfigs(
						validatorFactory, parentValidatorNodes,
						new HashMap<String, String>());
				validatorCfgs.addAll(parentValidatorCfgs);

				addFieldElements(fieldNodeMap, parentNode);
			}
		}
		List<ValidatorConfig> fieldValidatorCfgs = addFieldValidatorConfigs(
				validatorFactory, fieldNodeMap);
		validatorCfgs.addAll(fieldValidatorCfgs);

		return validatorCfgs;
	}

	/**
	 * @param fieldNodes
	 * @param parentNode
	 */
	private void addFieldElements(Map<String, Element> fieldNodeMap,
			Element parentNode) {
		NodeList fieldNodes = parentNode.getElementsByTagName("field");
		for (int j = 0; j < fieldNodes.getLength(); j++) {
			Element element = (Element) fieldNodes.item(j);
			String attrName = element.getAttribute("name");
			if (!fieldNodeMap.containsKey(attrName)) {
				fieldNodeMap.put(attrName, element);
			}
		}
	}

	/**
	 * @param factory
	 * @param validatorNodes
	 * @param extraParams
	 * @param validatorCfgs
	 */
	private List<ValidatorConfig> addValidatorConfigs(ValidatorFactory factory,
			NodeList validatorNodes, Map<String, String> extraParams) {
		List<ValidatorConfig> validatorCfgs = new ArrayList<ValidatorConfig>();
		for (int j = 0; j < validatorNodes.getLength(); j++) {
			Element validatorElement = (Element) validatorNodes.item(j);
			ValidatorConfig.Builder vCfg = buildCfg(factory, extraParams,
					validatorElement);
			validatorCfgs.add(vCfg.build());
		}
		return validatorCfgs;
	}

	/**
	 * @param factory
	 * @param validatorNodes
	 * @param extraParams
	 * @param validatorCfgs
	 */
	private List<ValidatorConfig> addFieldValidatorConfigs(
			ValidatorFactory factory, Map<String, Element> elementMap) {
		List<ValidatorConfig> validatorCfgs = new ArrayList<ValidatorConfig>();
		for (Entry<String, Element> entry : elementMap.entrySet()) {
			Element fieldElement = entry.getValue();
			// 设置Params(fieldName)
			String fieldName = fieldElement.getAttribute("name");
			Map<String, String> extraParams = new HashMap<String, String>();
			extraParams.put("fieldName", fieldName);

			// 设置Params(fieldLabelKey)
			String fieldLabelKey = fieldElement.getAttribute("labelKey");
			// 配置文件中有labelKey时，才设置fieldLabelKey参数
			boolean hasFieldLabelKey = (null != fieldLabelKey
					&& !"".equals(fieldLabelKey) && !"null"
					.equals(fieldLabelKey));
			if (hasFieldLabelKey) {
				extraParams.put("fieldLabelKey", fieldLabelKey);
			}

			NodeList fieldValidatorNodes = fieldElement
					.getElementsByTagName("field-validator");
			List<ValidatorConfig> fieldValidatorCfgs = addValidatorConfigs(
					factory, fieldValidatorNodes, extraParams);
			validatorCfgs.addAll(fieldValidatorCfgs);
		}
		return validatorCfgs;
	}

	/**
	 * @param factory
	 * @param extraParams
	 * @param validatorElement
	 * @return
	 */
	private ValidatorConfig.Builder buildCfg(ValidatorFactory factory,
			Map<String, String> extraParams, Element validatorElement) {
		String validatorType = validatorElement.getAttribute("type");
		Map<String, String> params = new HashMap<String, String>(extraParams);

		params.putAll(XmlHelper.getParams(validatorElement));

		// ensure that the type is valid...
		try {
			factory.lookupRegisteredValidatorType(validatorType);
		} catch (IllegalArgumentException ex) {
			throw new ConfigurationException("Invalid validation type: "
					+ validatorType, ex, validatorElement);
		}

		ValidatorConfig.Builder cfgBuilder = new ValidatorConfig.Builder(
				validatorType)
				.addParams(params)
				.location(DomHelper.getLocationObject(validatorElement))
				.shortCircuit(
						Boolean.valueOf(validatorElement
								.getAttribute("short-circuit")));

		NodeList messageNodes = validatorElement
				.getElementsByTagName("message");
		Element messageElement = (Element) messageNodes.item(0);

		if (null != messageElement) {
			final Node defaultMessageNode = messageElement.getFirstChild();
			String defaultMessage = (defaultMessageNode == null) ? ""
					: defaultMessageNode.getNodeValue();

			Map<String, String> messageParams = XmlHelper
					.getParams(messageElement);
			String key = messageElement.getAttribute("key");
			if ((key != null) && (key.trim().length() > 0)) {
				cfgBuilder.messageKey(key);
			}
			cfgBuilder.defaultMessage(defaultMessage);

			proccessMessage(cfgBuilder, messageParams);
		} else {
			// 将Action的参数设置为默认messageParams
			proccessMessage(cfgBuilder, params);
		}
		return cfgBuilder;
	}

	/**
	 * @param validatorType
	 * @param cfgBuilder
	 * @param messageParams
	 * @param key
	 */
	private void proccessMessage(ValidatorConfig.Builder cfgBuilder,
			Map<String, String> messageParams) {
		// Get the default message when pattern 2 is used. We are only
		// interested in the
		// i18n message parameters when an i18n message key is specified.
		// pattern 1:
		// <message key="someKey">Default message</message>
		// pattern 2:
		// <message key="someKey">
		// <param name="1">'param1'</param>
		// <param name="2">'param2'</param>
		// <param name="defaultMessage>The Default Message</param>
		// </message>
		if (messageParams.containsKey("defaultMessage")) {
			cfgBuilder.defaultMessage(messageParams.get("defaultMessage"));
		}

		// Sort the message param.
		Map<Integer, String> sortedMessageParameters = sortMessageParam(messageParams);
		cfgBuilder.messageParams(sortedMessageParameters.values().toArray(
				new String[sortedMessageParameters.values().size()]));
	}

	/**
	 * Sort the message param. those with keys as '1', '2', '3' etc. (numeric
	 * values) are i18n message parameter, others are excluded.
	 * 
	 * @param messageParams
	 * @return
	 */
	private Map<Integer, String> sortMessageParam(
			Map<String, String> messageParams) {
		// Sort the message param. those with keys as '1', '2', '3' etc.
		// (numeric values)
		// are i18n message parameter, others are excluded.
		Map<Integer, String> sortedMessageParameters = new TreeMap<Integer, String>();
		for (Map.Entry<String, String> messageParamEntry : messageParams
				.entrySet()) {

			try {
				int _order = Integer.parseInt(messageParamEntry.getKey());
				sortedMessageParameters.put(Integer.valueOf(_order),
						messageParamEntry.getValue());
			} catch (NumberFormatException e) {
				// ignore if its not numeric.
				logger.warn("message param is not number:"
						+ messageParamEntry.getKey());
			}
		}
		return sortedMessageParameters;
	}

	public void setValidatorContext(IValidatorContext ctx) {
		this.validatorContext = ctx;
	}
}
